﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SudokuGame.Sudoku;

namespace SudokuGame
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            this.Load += new EventHandler(MainForm_Load);
            this._LinkToPageLabel.LinkVisited = false;
            this._StatusReset();
        }

        void MainForm_Load(object sender, EventArgs e)
        {
            int gameCount = SudokuSheet.GamePresetCount;
            this._GameSelectorTrack.Minimum = 0;
            this._GameSelectorTrack.Maximum = gameCount - 1;
            this._SetToolTipGame(0, true);
            this._SudokuUI.Sheet = Sudoku.SudokuSheet.CreateSheet(this._GameSelectorTrack.Value);

            this.ResultControlVisible(false);
        }
        #region Game selector, Copy & Paste, Link
        void _GameSelectorTrack_ValueChanged(object sender, System.EventArgs e)
        {
            int value = this._GameSelectorTrack.Value;
            this._SudokuUI.Sheet = Sudoku.SudokuSheet.CreateSheet(value);

            this._SetToolTipGame(value, true);

            this._TextReset();
            this._StatusReset();
        }
        private void _CopyBtn_Click(object sender, EventArgs e)
        {
            if (this._SudokuUI.Sheet != null)
            {
                System.Windows.Forms.Clipboard.SetText(this._SudokuUI.Sheet.Serial);
                this.ShowText("Aktuální hra je umístěna ve schránce Windows.");
            }
        }
        private void _PasteBtn_Click(object sender, EventArgs e)
        {
            this._TextReset();
            try
            {
                string clip = System.Windows.Forms.Clipboard.GetText(TextDataFormat.Text);
                SudokuSheet sheet = SudokuSheet.CreateSheet(clip);
                if (sheet != null)
                {
                    this._SudokuUI.Sheet = sheet;
                    this._StatusReset();
                }
                else
                {
                    this.ShowText("Schránka Windows neobsahuje nic, co by bylo možno reprezentovat jako hru SUDOKU.");
                }
            }
            catch (Exception exc)
            {
                this.ShowText(exc.Message);
            }
        }
        private void _TextReset()
        {
            this.ShowText("");
        }
        private void _StatusReset()
        {
            this._StatusSet("SUDOKU verze 0.0 TK && Daj");
        }
        private void _StatusSet(string text)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action<string>(this._StatusSet), text);
            else
            {
                this._StatusLabel.Text = text;
                this._StatusStrip.Refresh();
            }
        }
        private void _LinkToPageLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            string link = this._LinkToPageLabel.Text.Substring(this._LinkToPageLabel.LinkArea.Start, this._LinkToPageLabel.LinkArea.Length);
            System.Diagnostics.Process.Start(link);
        }
        private void _AlwaysOnTopCheck_CheckedChanged(object sender, EventArgs e)
        {
            this.TopMost = this._AlwaysOnTopCheck.Checked;
        }
        private void _StartButton1_Click(object sender, EventArgs e)
        {
            this._StartWork(1);
        }
        private void _StartButton2_Click(object sender, EventArgs e)
        {
            this._StartWork(2);
        }
        private void _AbortButton_Click(object sender, EventArgs e)
        {
            this._AbortWork();
        }
        #endregion
        #region Práce poběží na pozadí
        private void _StartWork(int resolver)
        {
            if (this._WorkThread != null)
            {
                MessageBox.Show("Nelze spustit, výpočet ještě nedoběhl.");
                return;
            }
            this._ResolverType = resolver;
            this._SudokuUI.Clear();
            this.StartButtonEnabled(false);
            this._TextReset();
            this._StatusSet("Probíhá výpočet řešení...");
            this._WorkThread = new System.Threading.Thread(this._StartWorkBgr);
            this._WorkThread.Name = "SudokuResolver";

            this._WorkThread.Start();
        }
        private void _AbortWork()
        {
            System.Threading.Thread workThread = this._WorkThread;
            if (workThread != null)
            {
                workThread.Abort();
            }
        }
        private void _StartWorkBgr()
        {
            try
            {
                Sudoku.SudokuSheet game = this._SudokuUI.Sheet;

                this._ResolveBegin(game);

                List<SudokuSheet> results = null;
                switch (this._ResolverType)
                {
                    case 1:
                        results = Sudoku.SudokuResolver.Resolve(game, this._ResolveStep);
                        break;
                    case 2:
                        results = Sudoku.SudokuResolverDaj.Resolve(game, this._ResolveStep);
                        break;
                }

                this._ResolveDone(results);
            }
            catch (System.Threading.ThreadAbortException)
            {
                this._ResolveAbort();
            }
            catch (Exception exc)
            {
                this.ShowText(exc.Message + Environment.NewLine + exc.StackTrace);
                this._StatusReset();
            }
            finally
            {
                this._WorkThread = null;
                this.StartButtonEnabled(true);
            }
        }
        private void ShowText(string text)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action<string>(this.ShowText), text);
            else
                this._InfoText.Text = text;
        }
        private void StartButtonEnabled(bool enabled)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action<bool>(this.StartButtonEnabled), enabled);
            else
            {
                this._StartButton1.Enabled = enabled;
                this._StartButton2.Enabled = enabled;
                this._PasteBtn.Enabled = enabled;
                this._CopyBtn.Enabled = enabled;
                this._AbortButton.Visible = !enabled;
                this._AbortButton.Enabled = !enabled;
            }
        }
        private void _ResolveBegin(Sudoku.SudokuSheet game)
        {
            this._GameStepList = new List<SudokuSheet>();
            this._GameStepList.Add(game.Clone);
            this._GameResultList = new List<SudokuSheet>();
            this._GameResultList.Add(game.Clone);

            this._GameAttemptCount = 0;
            this._GameErrorCount = 0;

            this._GameWatch = new System.Diagnostics.Stopwatch();
            this._GameFrequency = (decimal)System.Diagnostics.Stopwatch.Frequency / 1000m;
            this._GameWatch.Start();

            this._ShowNextStepTime = DateTime.Now;
            this._ShowNextStepInterval = 50;

            this._GameStatusInfo = "";

            this.BeginInvoke(new Action<Sudoku.SudokuSheet>(this._ResolveBeginUI), game);
        }
        private void _ResolveBeginUI(Sudoku.SudokuSheet game)
        {
            this.ResultControlVisible(false);
            this.StartButtonEnabled(false);
        }
        private void _ResolveStep(object sender, SudokuProgressArgs args)
        {
            SudokuSheet sheet = args.Sheet;
            this._GameNodeCount = args.NodeCount;
            this._GameBrancheCount = args.BrancheCount;
            if (sheet != null && (this._GameStepList.Count == 0 || sheet.Serial != this._GameStepList[this._GameStepList.Count - 1].Serial))
            {   // Pokud je hra předaná, a pokud je jiná než posledně uložená, pak tuto novou uložíme taky:
                this._GameAttemptCount++;
                this._GameStepList.Add(sheet.Clone);
                if (sheet.State == SudokuState.HasError)
                    this._GameErrorCount++;
            }
            this._ResultPrepareStatusInfo();

            // Zobrazení jednoho kroku řešení (metoda this._ResolveStepUI()) zabere nějaký čas, 
            // a to jak ve výkonném threadu (current), tak i v threadu GUI (které invokujeme).
            // Navíc při skončení řešení se GUI aktualizuje (jak Game, tak Status).
            // Proto: budu GUI volat jen tehdy, když od posledního volání uběhl určitý čas (_ShowNextStepInterval = 50 milisekund).
            DateTime now = DateTime.Now;
            if (now >= this._ShowNextStepTime)
            {
                this.BeginInvoke(new Action<Sudoku.SudokuSheet>(this._ResolveStepUI), sheet);
                this._ShowNextStepTime = now.AddMilliseconds(this._ShowNextStepInterval);
            }
        }
        private void _ResolveStepUI(Sudoku.SudokuSheet game)
        {
            this._SudokuUI.Sheet = game;
            this._StatusLabel.Text = this._GameStatusInfo;
            this._StatusStrip.Refresh();
        }
        private void _ResolveAbort()
        {
            if (String.IsNullOrEmpty(this._GameStatusInfo))
                this._ResultPrepareStatusInfo();
            this.BeginInvoke(new Action(this._ResolveAbortUI));
            
        }
        private void _ResolveAbortUI()
        {
            this.ShowText("Operace byla přerušena.");
            this._ResolveDoneUI();
        }
        private void _ResolveDone(List<SudokuSheet> results)
        {
            if (results != null)
                this._GameResultList.AddRange(results);

            if (String.IsNullOrEmpty(this._GameStatusInfo))
                this._ResultPrepareStatusInfo();
            this.BeginInvoke(new Action(this._ResolveDoneUI));
        }
        private void _ResolveDoneUI()
        {
            int resultCount = this._GameResultList.Count;
            this._ResultVariationIdx.Minimum = 0;
            this._ResultVariationIdx.Maximum = (resultCount == 0 ? 0 : resultCount - 1);
            this._ResultVariationIdx.Value = this._ResultVariationIdx.Maximum;

            int stepCount = this._GameStepList.Count;
            int step = (stepCount == 0 ? 0 : stepCount - 1);
            int tickFq = (stepCount <= 100 ? 1 : stepCount / 100);
            this._GameProgressTrack.Minimum = 0;
            this._GameProgressTrack.Maximum = step;
            this._GameProgressTrack.TickFrequency = tickFq;
            this._GameProgressTrack.Value = step;

            this._SetToolTipStep(step, false);

            this._StatusLabel.Text = this._GameStatusInfo;
            this._StatusStrip.Refresh();

            Sudoku.SudokuSheet game = null;
            if (resultCount > 0)
            {
                game = this._GameResultList[resultCount - 1];
                this._FindProgressStep(game);
            }
            else if (stepCount > 0)
            {
                game = this._GameStepList[stepCount - 1];
            }
            this.ResultControlVisible(true);
            this._SudokuUI.Clear();
            this._SudokuUI.Sheet = game;
        }
        private void _ResultPrepareStatusInfo()
        {
            decimal milisecs = (decimal)this._GameWatch.ElapsedTicks / _GameFrequency;
            this._GameStatusInfo =
                "Krok číslo " + this._GameAttemptCount.ToString() + ".  " +
                "Počet křižovatek " + this._GameNodeCount.ToString() + ", " +
                "počet cest " + this._GameBrancheCount.ToString() + ".  " +
                "Čas celkem " + milisecs.ToString("## ### ##0.00").Trim() + " milisec. " +
                "Počet chyb " + this._GameErrorCount.ToString();
        }
        private void ResultControlVisible(bool visible)
        {
            this._ResultVariationIdx.Visible = visible;
            this._ResultVariationLabel.Visible = visible;
            this._GameProgressTrack.Visible = visible;
            this._GameProgressLabel.Visible = visible;
        }
        private System.Threading.Thread _WorkThread;
        private int _ResolverType;
        private List<Sudoku.SudokuSheet> _GameStepList;
        private List<SudokuSheet> _GameResultList;
        private int _GameAttemptCount;
        private int _GameErrorCount;
        private int _GameNodeCount;
        private int _GameBrancheCount;
        private System.Diagnostics.Stopwatch _GameWatch;
        private decimal _GameFrequency;
        private DateTime _ShowNextStepTime;
        private double _ShowNextStepInterval;
        private string _GameStatusInfo;
        #endregion
        #region Zobrazení výsledku (kroky a řešení)
        /// <summary>
        /// Po změně hodnoty v trackbaru, který představuje kroky řešení
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _GameProgressTrack_ValueChanged(object sender, EventArgs e)
        {
            this._ShowProgressStep();
        }
        private void _ShowProgressStep()
        {
            if (this._GameProgressTrack.Visible)
            {
                int value = this._GameProgressTrack.Value;
                if (value >= 0 && this._GameStepList != null && value < this._GameStepList.Count)
                    this._SudokuUI.SheetChanged = this._GameStepList[value];

                this._SetToolTipStep(value, true);
            }
        }
        /// <summary>
        /// Po změně hodnoty v indexu, který představuje varianty výsledku
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _ResultStepIdx_ValueChanged(object sender, EventArgs e)
        {
            this._ShowResultVariation();
        }
        private void _ResultVariationIdx_Scroll(object sender, ScrollEventArgs e)
        {
            this._ShowResultVariation();
        }
        private void _ResultVariationIdx_MouseClick(object sender, MouseEventArgs e)
        {
            this._ShowResultVariation();
        }
        private void _ShowResultVariation()
        {
            if (this._ResultVariationIdx.Visible)
            {
                int value = (int)this._ResultVariationIdx.Value;
                if (value >= 0 && this._GameResultList != null && value < this._GameResultList.Count)
                {
                    Sudoku.SudokuSheet game = this._GameResultList[value];
                    this._FindProgressStep(game);
                    this._SudokuUI.Sheet = this._GameResultList[value];
                }
            }
        }
        private void _FindProgressStep(SudokuSheet game)
        {
            string serial = game.Serial;
            int step = this._GameStepList.FindIndex(g => g.Serial == serial);
            if (step >= 0)
            {
                this._GameProgressTrack.Value = step;
                this._SetToolTipStep(step, false);
            }
        }
        private void _SetToolTipGame(int value, bool show)
        {
            show = false;
            string text = "Varianta: " + value.ToString() + Environment.NewLine + _ToolTipGame;
            this._ToolTip.SetToolTip(this._GameSelectorTrack, text);
            if (show)
                this._ToolTip.Show(text, this);
        }
        private void _SetToolTipStep(int value, bool show)
        {
            show = false;
            string text = "Krok: " + value.ToString() + Environment.NewLine + _ToolTipStep;
            this._ToolTip.SetToolTip(this._GameProgressTrack, text);
            if (show)
                this._ToolTip.Show(text, this);
        }

        private const string _ToolTipGame = @"Lze zvolit zadání hry z několika přednastavených her.";
        private const string _ToolTipStep = @"Obsahuje zachycené jednotlivé kroky řešení,
tak jak je řešící metoda postupně nalezla a předala k zobrazení.";
        private const string _ToolTipResult = @"Pod číslem 0 je zadání.
Pod číslem 1 je řešení.
Pokud některá hra má víc platných výsledků, lze je zobrazit změnou čísla.";
        #endregion
    }
}
